package cn.ronghuanet.car.service.impl;

import cn.ronghuanet.base.config.es.HighlightResultMapper;
import cn.ronghuanet.base.util.PageInfo;
import cn.ronghuanet.base.util.SearchUtil;
import cn.ronghuanet.car.doc.CarDoc;
import cn.ronghuanet.car.query.CarSearchQuery;
import cn.ronghuanet.car.repository.CarDocRepository;
import cn.ronghuanet.car.service.ICarSearchService;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.time.DateUtils;
import org.elasticsearch.index.query.BoolQueryBuilder;
import org.elasticsearch.index.query.MultiMatchQueryBuilder;
import org.elasticsearch.index.query.QueryBuilder;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.search.aggregations.AggregationBuilders;
import org.elasticsearch.search.aggregations.Aggregations;
import org.elasticsearch.search.aggregations.BucketOrder;
import org.elasticsearch.search.aggregations.bucket.terms.TermsAggregationBuilder;
import org.elasticsearch.search.fetch.subphase.highlight.HighlightBuilder;
import org.elasticsearch.search.sort.SortBuilders;
import org.elasticsearch.search.sort.SortOrder;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.elasticsearch.core.ElasticsearchTemplate;
import org.springframework.data.elasticsearch.core.aggregation.AggregatedPage;
import org.springframework.data.elasticsearch.core.query.NativeSearchQueryBuilder;
import org.springframework.stereotype.Service;

import javax.smartcardio.Card;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.Objects;

@Service
public class CarSearchServiceImpl implements ICarSearchService {

    @Autowired
    private CarDocRepository carDocRepository;

    @Autowired
    private HighlightResultMapper highlightResultMapper;

    @Autowired
    private ElasticsearchTemplate elasticsearchTemplate;

    /**
     * 从es中进行车辆信息的查询
     * query -> bool -> must(match)/filter(range)
     * @param query
     * @return
     */
    @Override
    public PageInfo<CarDoc> search(CarSearchQuery query) {
        // 创建查询构建器对象
        NativeSearchQueryBuilder queryBuilder = new NativeSearchQueryBuilder();
        // 1 添加查询条件
        BoolQueryBuilder boolQuery = QueryBuilders.boolQuery();
        // 1.1 关键字查询(一个关键字要匹配多个字段) - 分词查询
        List<QueryBuilder> must = boolQuery.must();
        if(StringUtils.isNotBlank(query.getKeyword())){
            MultiMatchQueryBuilder matchQuery = QueryBuilders.multiMatchQuery(query.getKeyword(),
                    "title", "shopName", "shopAddress", "typeName", "carInfo");
            must.add(matchQuery);
        }
        List<QueryBuilder> filter = boolQuery.filter();
        // 1.2 类型查询 typeId -> 等值查询
        if(Objects.nonNull(query.getTypeId())){
            filter.add(QueryBuilders.termQuery("typeId",query.getTypeId()));
        }

        // 1.3 价格  costprice >= minPrice costprice < maxPrice
        if(Objects.nonNull(query.getMinPrice())){
            filter.add(QueryBuilders.rangeQuery("costprice").gte(query.getMinPrice()));
        }
        if(Objects.nonNull(query.getMaxPrice())){
            filter.add(QueryBuilders.rangeQuery("costprice").lt(query.getMaxPrice()));
        }

        // 1.4 车龄  carAge  carAgeType  reigstertime
        // 举例说明  如果想查询5年以上,要先通过当前时间-5年得到5年前的时间点
        // 比如今天2023-07-05  5年前:2018-7-5
        // 5年以上  应该在18年以前  reigstertime < 2018-7-5
        // 5年以内  在18年以后  reigstertime>18-7-5
        if(Objects.nonNull(query.getCarAge())){
            // 要先得到车龄的时间节点  当前时间-carAge
            Date date = DateUtils.addYears(new Date(), 0 - query.getCarAge());
            // 判断carAgeType
            if(query.getCarAgeType() == 1){
                // 因为es中是将时间存成了时间戳,所以在进行查询时,时间也要转为时间戳才可以
                filter.add(QueryBuilders.rangeQuery("reigstertime").lt(date.getTime()));
            }else{
                filter.add(QueryBuilders.rangeQuery("reigstertime").gte(date.getTime()));
            }
        }

        // 1.5 其他查询条件
        if(Objects.nonNull(query.getCosteffective())){
            filter.add(QueryBuilders.termQuery("costeffective",query.getCosteffective()));
        }
        if(Objects.nonNull(query.getRushsale())){
            filter.add(QueryBuilders.termQuery("rushsale",query.getRushsale()));
        }
        if(Objects.nonNull(query.getTransitivecountry())){
            filter.add(QueryBuilders.termQuery("transitivecountry",query.getTransitivecountry()));
        }
        if(Objects.nonNull(query.getQuasinewcar())){
            filter.add(QueryBuilders.termQuery("quasinewcar",query.getQuasinewcar()));
        }

        queryBuilder.withQuery(boolQuery);

        // 2 添加排序条件
        SortOrder sortOrder = SortOrder.ASC;    // 默认按照正序排序
        if("desc".equals(query.getSortType())){
            sortOrder = SortOrder.DESC;
        }
        queryBuilder.withSort(SortBuilders.fieldSort(query.getSortField()).order(sortOrder));

        // 3 做分页查询
        // page:页码,但是它是当前页码-1才是查询的当前页数据
        // size:每页显示条数
        int page = query.getCurrentPage().intValue();
        int size = query.getPageSize().intValue();
        queryBuilder.withPageable(PageRequest.of(page-1,size));

        // 4 高亮展示
        HighlightBuilder.Field field1 = new HighlightBuilder.Field("title").preTags("<span style='color:red;font-weight:bold'>")
                .postTags("</span>");
        HighlightBuilder.Field field2 = new HighlightBuilder.Field("shopName").preTags("<span style='color:red;font-weight:bold'>")
                .postTags("</span>");
        HighlightBuilder.Field field3 = new HighlightBuilder.Field("shopAddress").preTags("<span style='color:red;font-weight:bold'>")
                .postTags("</span>");
        HighlightBuilder.Field field4 = new HighlightBuilder.Field("typeName").preTags("<span style='color:red;font-weight:bold'>")
                .postTags("</span>");
        HighlightBuilder.Field field5 = new HighlightBuilder.Field("carInfo").preTags("<span style='color:red;font-weight:bold'>")
                .postTags("</span>");
        queryBuilder.withHighlightFields(field1,field2,field3,field4,field5);

        // 5 做聚合查询
        // 按照typeId来进行聚合分组查询并得到每个ID的名字
        TermsAggregationBuilder aggregation = AggregationBuilders.terms("typeIdGroup")
                .field("typeId").order(BucketOrder.count(false))
                .subAggregation(AggregationBuilders.terms("typeNameGroup").field("typeName.keyword")
                        .order(BucketOrder.count(false)));
        // 按照店铺ID来进行分组聚合查询,并要获取每个店铺ID的名字
        TermsAggregationBuilder aggregation1 = AggregationBuilders.terms("shopIdGroup")
                .field("shopId").order(BucketOrder.count(false))
                .subAggregation(AggregationBuilders.terms("shopNameGroup").field("shopName.keyword")
                        .order(BucketOrder.count(false)));

        queryBuilder.addAggregation(aggregation);
        queryBuilder.addAggregation(aggregation1);

        // 通过查询构建器得到查询对象,通过查询对象做es的查询
        // 如果想用自己的结果映射器对象的话,repository接口是没有提供方法的,要用ElasticsearchTemplate才可以
        //Page<CarDoc> docPage = carDocRepository.search(queryBuilder.build());
        AggregatedPage<CarDoc> docPage = elasticsearchTemplate.queryForPage(queryBuilder.build(), CarDoc.class, highlightResultMapper);

        // 将docPage转换成我们自己的分页对象,返回给前端
        List<CarDoc> rows = docPage.getContent();   // 当前页数据
        if(rows != null && rows.size() > 0){
            for (CarDoc row : rows) {
                // 处理店铺地址的格式
                String shopAddress = row.getShopAddress();
                if(StringUtils.isNotBlank(shopAddress)){
                    shopAddress = shopAddress.substring(0,shopAddress.indexOf("区")+1);
                    row.setShopAddress(shopAddress);
                }
            }
        }
        long total = docPage.getTotalElements();
        PageInfo<CarDoc> pageInfo = new PageInfo<>(total, rows);

        // docPage中已经包含了查询出来的聚合结果了,要将聚合结果取出来放到PageInfo的map中
        Aggregations aggregations = docPage.getAggregations();
        Map<String, Object> map = SearchUtil.handleTermsAggsData(aggregations);
        pageInfo.setMap(map);
        return pageInfo;
    }
}
